/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2016年3月10日
 * V4.0
 */
package com.jphenix.servlet.filter;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jphenix.driver.nodehandler.FNodeHandler;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Register;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequestManager;
import com.jphenix.standard.servlet.IResponseManager;
import com.jphenix.standard.servlet.api.IFilterConfig;
import com.jphenix.standard.servlet.api.IRequest;
import com.jphenix.standard.servlet.api.IResponse;
import com.jphenix.standard.viewhandler.IViewHandler;

/**
 * 域名跳转过滤器
 * 
 * com.jphenix.servlet.filter.HostFilter
 * 
 * 
 * <!-- 域名跳转过滤器 -->
 *  <host_filter>
 *        <value host="www.chenxinsoft.com" >/web_issue/chenxin</value>
 *       <value host="chenxinsoft.com" >/web_issue/chenxin</value>
 *        <value host="www.jphenix.org" >/web_issue/jphenix</value>
 *       <value host="jphenix.org" >/web_issue/jphenix</value>
 *        <value host="www.jphenix.com" >/web_issue/jphenix</value>
 *        <value host="jphenix.com" >/web_issue/jphenix</value>
 *        
 *        <use_native_file_servlet>1</use_native_file_servlet> <!-- 是否需要用到内置的文件服务，有的环境不支持servlet容器处理跳转后的文件url -->
 *       <exclude_ext_name>ha,htm,html</exclude_ext_name> <!-- 如果采用内置的文件服务，排除哪些扩展名，用半角逗号分隔 -->
 *        <welcome_file>index.html,index.htm</welcome_file> <!-- 如果需要用到内置文件服务，需要指定默认访问文件，多个用半角逗号分隔 -->
 *   </host_filter>
 *   
 *   
 * 2018-06-23 支持页面中的文件路径前增加@，可以加载根文件夹（真正的根文件夹，非域名对应的根文件夹），实现多个域名下的程序共享资源。
 * 2019-06-15 按照IFilter增加了过滤器初始化方法
 * 2019-08-24 由于request对象去掉了setPathTranslated方法
 * 2022-09-04 隔离了ServletApi，兼容新老Tomcat
 * 2022-09-05 修改了发现的错误
 *   
 *   
 *   
 *   
 * 通过预先配置的信息，不同域名或者虚拟路径，跳转到不同的根路径
 * @author 马宝刚
 * 2016年3月10日
 */
@ClassInfo({"2022-09-08 10:58","域名跳转过滤器"})
@BeanInfo({"hostfilter"})
@Register({"filtervector"})
@Running({"1"})
public class HostFilter extends ABase implements IFilter {

	protected BaseFilter                   bf                    = null;  //过滤器管理类
    protected IFilterConfig                config                = null;  //过滤器配置信息处理类
    private Map<String,String>             publicContextMap      = null;  //全局域名筛选相对路径对照容器
    private Map<String,String>             hostMap               = null;  //指定域名筛选相对路径对照容器
    private Map<String,Map<String,String>> hostContextMap        = null;  //指定域名并指定上下文的对照容器
    private boolean                        useNativeFileServlet  = false; //是否使用内置的文件服务
    private List<String>                   excludeExtNameList    = null;  //如果使用内置文件服务，不处理的扩展名列表
    private List<String>                   welcomeFileList       = null;  //如果使用内置文件服务，默认访问文件序列
    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public HostFilter() {
        super();
    }
    
	/**
	 * 执行初始化
	 * 
     * 配置文件格式：
     * 
     * <host_filter>
     *      <value host="域名  可选“  context=”上下文，斜杠开头  可选“>相对于默认根路径的指定路径</value>
     *      <value host="域名  可选“  context=”上下文，斜杠开头  可选“>相对于默认根路径的指定路径</value>
     * </host_filter>
	 * 
	 * @param bf         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public void init(BaseFilter bf, IFilterConfig config) throws Exception {
		this.bf          = bf;
		this.config      = config;
        publicContextMap = new HashMap<String,String>();
        hostMap          = new HashMap<String,String>();
        hostContextMap   = new HashMap<String,Map<String,String>>();
        
        //获取集群服务器信息
        IViewHandler hostInfoVh = prop.getParameterXml("host_filter");
        //获取配置信息 <value host="域名“  context=”上下文，斜杠开头“>相对于默认根路径的指定路径</value>
        List<IViewHandler> hostInfoList = hostInfoVh.getChildNodesByNodeName("value");
        String hostName;        //访问的域名
        String contextName;    //虚拟路径名
        String subPath;             //对应的路径
        for(IViewHandler vh:hostInfoList) {
            hostName = vh.a("host");
            contextName = vh.a("context");
            subPath = vh.nt();
            
            if(subPath.length()<1) {
                //没有指定相对路径，那还扯什么
                continue;
            }
            if(hostName.length()<1) {
                //全局跳转信息
                if(contextName.length()<1) {
                    continue;
                }
                publicContextMap.put(contextName,subPath);
                continue;
            }
            if(contextName.length()<1) {
                hostMap.put(hostName,subPath);
                continue;
            }
            //获取指定域名的上下文对照容器
            Map<String,String> ctxMap = hostContextMap.get(hostName);
            if(ctxMap==null) {
                ctxMap = new HashMap<String,String>();
                hostContextMap.put(hostName,ctxMap);
            }
            ctxMap.put(contextName,subPath);
        }
        //是否使用内置的文件服务
        useNativeFileServlet = boo(hostInfoVh.fnn("use_native_file_servlet").nt());
        //如果使用内置文件服务，不处理的扩展名列表
        excludeExtNameList = BaseUtil.splitToList(hostInfoVh.fnn("exclude_ext_name").nt(),",");
        
        //如果使用内置文件服务，默认访问文件序列
        welcomeFileList = BaseUtil.splitToList(hostInfoVh.fnn("welcome_file").nt(),",");
        if(welcomeFileList.size()<1) {
        	//获取配置文件 WEB-INF/web.xml 对象
        	IViewHandler vh = FNodeHandler.newFile(new File(filesUtil.getAllFilePath("web.xml")));
        	if(vh.hasChildNodeByNodeName("welcome-file-list")) {
        		vh = vh.getFirstChildNodeByNodeName("welcome-file-list");
        		//元素对象
        		List<IViewHandler> cList = vh.getChildNodesByNodeName("welcome-file");
        		String value; //值
        		for(IViewHandler ele:cList) {
        			value = ele.nt().trim();
        			if(value.length()>0) {
        				welcomeFileList.add(value);
        			}
        		}
        	}
        	
        }
    
	}
	
    /**
     * 获取动作扩展名
     * @param req 页面请求
     * @return 扩展名
     * 2014年9月12日
     * @author 马宝刚
     */
    private String getActionExtName(IRequest req) {
        String action = req.getServletPath();     //动作路径
        int point     = action.lastIndexOf(".");  //动作扩展名分割点
        if(point<0) {
            return "";
        }
        return action.substring(point+1);
    }
    
    /**
     * 覆盖方法
     */
    @Override
    public int getIndex() {
        return 2;
    }

    /**
     * 覆盖方法
     */
    @Override
    public String getFilterActionExtName() {
        return "*";
    }

    /**
     * 覆盖方法
     */
    @Override
    public boolean doFilter(IRequestManager req, IResponseManager resp) throws Exception {
        String host        = req.getServerName();   //获取请求域名
        String ctx         = req.getContextPath();  //获取上下文名
        String subPath     = null;                  //跳转到的路径 
        String extName     = getActionExtName(req); //获取url扩展名
        String servletPath = req.getServletPath();  //请求路径
        if(hostContextMap.containsKey(host)) {
            //获取上下文对照容器
            Map<String,String> ctxMap = hostContextMap.get(host);
            if(ctxMap!=null) {
                subPath = str(ctxMap.get(ctx));
                if(subPath.length()<1) {
                    subPath = str(ctxMap.get("*"));
              }
            }
        }else if(hostMap.containsKey(host)) {
            subPath = str(hostMap.get(host));
        }else if(publicContextMap.containsKey(ctx)) {
            subPath = publicContextMap.get(ctx);
        }
        if(subPath==null || subPath.length()<1) {
            return false;
        }
        String pathTranslated; // 文件实际路径
        if(servletPath.startsWith("/@")) {
        	//使用真实的根路径资源
        	servletPath = "/"+servletPath.substring(2);
        	req.setServletPath(servletPath);
        	pathTranslated = path(servletPath);
        }else {
            //获取URL对应的实际文件路径
            pathTranslated = path(subPath+servletPath);
            if(servletPath.equals("/")) {
                for(String fName:welcomeFileList) {
                    if((new File(pathTranslated+fName)).exists()) {
                        if(useNativeFileServlet) {
                        	req.setServletPath(servletPath+fName);
                        	break;
                        }else {
                            resp.sendRedirect(servletPath+fName);
                            return true;
                        }
                    }
                }
            }else {
            	req.setServletPath(subPath+servletPath);
            }
        }
        
        //经过多次尝试，无需设置下面两个属性值，设置了反而无法显示
        //req.setPathInfo(subPath+req.getServletPath());
        //req.setRequestURI(("/".equals(req.getContextPath())?"":req.getContextPath())+req.getServletPath());

        if(useNativeFileServlet && !excludeExtNameList.contains(extName)) {
        	serveFile(pathTranslated,req,resp);
            return true;
        }
        return false;
    }
    /**
     * 输出指定文件内容到页面中
     * 刘虻
     * 2012-7-27 上午9:09:36
     * @param filePath     文件实际路径
     * @param req          页面请求
     * @param res          页面反馈
     * @return             返回是否终止继续处理 true终止继续处理
     * @throws IOException 异常
     */
    private void serveFile(
    		 String    filePath
            ,IRequest  req
            ,IResponse res) throws Exception {
    	// 构建文件对象
    	File file = new File(filePath);
        if (!file.canRead()) {
            res.sendError(IResponse.SC_FORBIDDEN,req);
            return;
        } else {
            try {
                file.getCanonicalPath();
            } catch (Exception e) {
                res.sendError(IResponse.SC_FORBIDDEN,
                        "Forbidden, exception:" + e,req);
                return;
            }
        }
        res.setStatus(IResponse.SC_OK);
        //设置内容类型
        res.setContentType(config.getServletContext().getMimeType(file.getName()));
        
        res.setHeader("Cache-Control", "no-cache");
        res.setHeader("Pragma", "no-cache");
        res.setDateHeader("Expires", 0); 
        res.setContentLengthLong(file.length());
        res.setDateHeader("Last-modified", file.lastModified());
        
        InputStream fis  = null;
        OutputStream fos = null;
        try {
        	fis = new FileInputStream(file);
        	fos = res.iGetOutputStream();
            int byteData;
        	while((byteData=fis.read())!=-1) {
        		fos.write(byteData);
        	}
        	fos.flush();
        }catch(Exception e) {
        	e.printStackTrace();
        }finally {
        	try {
        		fis.close();
        	}catch(Exception e2) {}
        	try {
        		fos.close();
        	}catch(Exception e2) {}
        }
    }
    
	/**
	 * 覆盖方法
	 */
	@Override
	public void destroy() {}
}
